#include "glviewport.h"
#include <QDebug>
#include <cmath>

const float DEG2RAD = 3.14159/180;
enum {V_IDLE=0,V_TRANSLATING,V_ROTATING_0,V_ROTATING_1,V_SCALING_0,V_SCALING_1, V_NOFOCUS, V_BOXSELECTION};

GLViewport::GLViewport(QList<AED_Entity>* ents, EntityModel *model, QWidget *parent) :
    QGLWidget(parent),m_ents(ents), m_model(model)
{
    m_cam_x = m_cam_y = 0;
    m_zoom = 16;
    m_ratio = 1;
    m_height = 1;
    m_width = 1;
    m_snap =0;
    m_shift = 0;
    m_focused = false;
    m_state = V_IDLE;
    this->setMouseTracking(true);
    this->setFocusPolicy(Qt::WheelFocus);
}

void GLViewport::initializeGL(){
    qglClearColor(QColor::fromRgbF(0.5,0.5,0.5));
    glClearDepth(1.0f);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);

    glEnable(GL_BLEND);



    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

}

void GLViewport::resizeGL(int w, int h){
    //int side = min(w, h);
    m_height=h;
    m_width = w;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    m_ratio = w/float(h);
    glOrtho(-m_ratio*m_zoom, m_ratio*m_zoom, -m_zoom, m_zoom, -10, 10);

    glMatrixMode(GL_MODELVIEW);


}

void GLViewport::paintGL(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glTranslatef(-m_cam_x,-m_cam_y,0);


    glColor3f(0.5,0.5,0.5);


    // GRID
    glLineWidth(1);
    glBegin(GL_LINES);

        for(int i=0;i<m_zoom*2;i++){
            glVertex3f(-m_zoom*2+int(m_cam_x), int(i-m_zoom+(m_cam_y)),  0.0f);
            glVertex3f( m_zoom*2+int(m_cam_x), int(i-m_zoom+(m_cam_y)),  0.0f);
        }

        for(int i=0;i<int(m_zoom*m_ratio)*2;i++){

            glVertex3f( i-int(m_zoom*m_ratio)+int(m_cam_x), -m_zoom*2+int(m_cam_y),  0.0f);
            glVertex3f( i-int(m_zoom*m_ratio)+int(m_cam_x), m_zoom*2+int(m_cam_y),  0.0f);
        }

    glEnd();

    glLineWidth(2);
    if(m_focused)
        glColor3f(0.3,0.3,0.5);
    else
        glColor3f(0.4,0.4,0.4);
    glBegin(GL_LINES);
        glVertex3f(0,-m_zoom*2+int(m_cam_y),0);
        glVertex3f(0,m_zoom*2+int(m_cam_y),0);

        glVertex3f(-m_zoom*2+int(m_cam_x),0,0);
        glVertex3f(m_zoom*2+int(m_cam_x),0,0);
    glEnd();

    glLineWidth(1);

    // ENTITIES
    for(int layer=0;layer<10;layer++){
        for(int i=0; i<m_ents->size(); i++){

            AED_Entity ent = (*m_ents)[i];

            glPushMatrix();
                glTranslatef(ent.m_pos_x, ent.m_pos_y,0);
                glScalef(ent.m_size, ent.m_size,1);
                glRotated(ent.m_rot,0,0,1);

                AED_Image *img = ent.GetArchtype()->GetRenderComp()->GetImage();
                if(img&&ent.GetArchtype()->GetRenderComp()->GetLayer()==layer&&ent.GetArchtype()->GetRenderComp()->IsEnabled()){
                    glEnable(GL_TEXTURE_2D);
                    glPushMatrix();
                    int x_div = img->GetXDiv();
                    int y_div = img->GetYDiv();
                    int frame=0;
                    int col=frame%x_div;
                    int row=frame/x_div;

                    glColor4f(1,1,1,ent.GetArchtype()->GetRenderComp()->GetAlpha());
                    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
                    glBindTexture(GL_TEXTURE_2D, img->GetTexName());
                    float width = img->GetWidth()/(float)x_div;
                    float height = img->GetHeight()/(float)y_div;
                    float xratio = 1.0f;
                    float yratio = 1.0f;

                    if(width > height){
                        yratio = height/width;
                    }else{
                        xratio = width/height;
                    }

                    float c1 = col/(float)x_div;
                    float c2 = (col+1)/(float)x_div;
                    float r1 = 1-(row+1)/(float)y_div;
                    float r2 = 1-row/(float)y_div;

                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);


                    glBegin(GL_QUADS);
                        glTexCoord2f(c1, r1); glVertex3f(-0.5f*xratio, -0.5f*yratio,  0.0f);
                        glTexCoord2f(c2, r1); glVertex3f( 0.5f*xratio, -0.5f*yratio,  0.0f);
                        glTexCoord2f(c2, r2); glVertex3f( 0.5f*xratio,  0.5f*yratio,  0.0f);
                        glTexCoord2f(c1, r2); glVertex3f(-0.5f*xratio,  0.5f*yratio,  0.0f);
                    glEnd();
                    glPopMatrix();
                    glDisable(GL_TEXTURE_2D);
                }


                glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);

                if(ent.IsSelected()){
                    if(m_focused)
                        glColor3f(0,1,0);
                    else
                        glColor3f(0.3,0.3,0.8);
                }else{
                    if(m_focused)
                        glColor3f(0.1,0.1,0.5);
                    else
                        glColor3f(0.5,0.3,0.5);
                }

                glBegin(GL_QUADS);
                    glVertex3f(-0.5f, -0.5f,  0.0f);
                    glVertex3f( 0.5f, -0.5f,  0.0f);
                    glVertex3f( 0.5f,  0.5f,  0.0f);
                    glVertex3f(-0.5f,  0.5f,  0.0f);
                glEnd();


                AED_PhysicsComp *pcomp = ent.GetArchtype()->GetPhysicsComp();
                if(pcomp->IsEnabled()){
                    float shapesizex = pcomp->GetShapeSizeX();
                    float shapesizey = pcomp->GetShapeSizeY();

                    glEnable(GL_LINE_STIPPLE);
                    glLineStipple(1,0x6DB6);
                    if(ent.IsSelected()){
                        if(m_focused)
                            glColor3f(0.6,0.0,0.7);
                        else
                            glColor3f(0.3,0.2,0.4);
                    }else{
                        if(m_focused)
                            glColor3f(0.4,0.0,0.5);
                        else
                            glColor3f(0.3,0.3,0.3);
                    }

                    if(pcomp->GetShape()=="polygon"){

                        QList<AED_PhysicsComp::Point> *points = pcomp->GetPoints();

                        glBegin(GL_POLYGON);
                            for(int i=0;i<points->size();i++){
                                glVertex3f((*points)[i].x*shapesizex,(*points)[i].y*shapesizey,0);
                            }
                        glEnd();
                    }else if(pcomp->GetShape()=="box"){
                        glPushMatrix();
                        glScalef(shapesizex,shapesizey,0);
                        glBegin(GL_QUADS);
                            glVertex3f(-0.5,-0.5,0);
                            glVertex3f(0.5,-0.5,0);
                            glVertex3f(0.5,0.5,0);
                            glVertex3f(-0.5,0.5,0);
                        glEnd();
                        glPopMatrix();
                    }else if(pcomp->GetShape()=="circle"){
                        glPushMatrix();
                        glScalef(shapesizex,shapesizex,0);
                        glBegin(GL_LINE_LOOP);
                        for (int i=0;i<360;i++)
                        {
                           float degInRad = i*DEG2RAD;
                           glVertex2f(cos(degInRad)*0.5,sin(degInRad)*0.5);
                        }
                        glEnd();
                        glPopMatrix();
                    }
                    glDisable(GL_LINE_STIPPLE);
                }


            glPopMatrix();
        }
    }
    if(m_state==V_BOXSELECTION){
        float x1,x2,y1,y2;
        if(m_c.x()<m_c2.x()){
            x1 = m_c.x();
            x2 = m_c2.x();
        }else{
            x1 = m_c2.x();
            x2 = m_c.x();
        }

        if(m_c.y()<m_c2.y()){
            y1 = m_c.y();
            y2 = m_c2.y();
        }else{
            y1 = m_c2.y();
            y2 = m_c.y();
        }
        glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
        glEnable(GL_LINE_STIPPLE);
        glLineStipple(1,0x6DB6);
        glColor3f(0,0,0);
        glBegin(GL_QUADS);
            glVertex3f(x1,y1,0);
            glVertex3f(x2,y1,0);
            glVertex3f(x2,y2,0);
            glVertex3f(x1,y2,0);
        glEnd();
        glDisable(GL_LINE_STIPPLE);

    }else if(m_state==V_ROTATING_1||m_state==V_SCALING_1){
        glEnable(GL_LINE_STIPPLE);
        glLineStipple(1,0x6DB6);
        glColor3f(0,0,0);
        glBegin(GL_LINES);
            glVertex3f(m_ox,m_oy,0);
            glVertex3f(m_c2.x(),m_c2.y(),0);
        glEnd();
        glDisable(GL_LINE_STIPPLE);
    }
    glPopMatrix();


}

void GLViewport::mousePressEvent(QMouseEvent *event)
 {


    if(m_state==V_IDLE){
        if(event->button()==Qt::LeftButton){
            if(!m_shift){
                QPointF pos = Mouse2Screen(event->pos());
                SelectEntity(pos.x(),pos.y());
            }else{
                QPointF pos = Mouse2Screen(event->pos());
                SelectEntityPlus(pos.x(),pos.y());
            }
        }
    }else if(m_state==V_TRANSLATING){
        if(event->button()==Qt::RightButton){
            for(int i=0;i<m_temp_ents.size();i++){
                AED_Entity *ent = m_temp_ents[i];
                ent->m_pos_x=ent->o_pos_x;
                ent->m_pos_y=ent->o_pos_y;

            }
        }
    }else if(m_state==V_ROTATING_1){
        if(event->button()==Qt::RightButton){
            for(int i=0;i<m_temp_ents.size();i++){
                AED_Entity *ent = m_temp_ents[i];
                ent->m_pos_x=ent->o_pos_x;
                ent->m_pos_y=ent->o_pos_y;
                ent->m_rot=ent->o_rot;

            }
        }
    }else if(m_state==V_SCALING_1){
        if(event->button()==Qt::RightButton){
            for(int i=0;i<m_temp_ents.size();i++){
                AED_Entity *ent = m_temp_ents[i];
                ent->m_pos_x=ent->o_pos_x;
                ent->m_pos_y=ent->o_pos_y;
                ent->m_size=ent->o_size;

            }
        }
    }

    last_pos = event->pos();

    m_state = V_IDLE;
    updateGL();
 }

void GLViewport::wheelEvent(QWheelEvent *event){
    if(event->orientation() == Qt::Vertical){
        float delta = m_zoom/(event->delta());
        m_zoom -= delta*40;

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(-m_ratio*m_zoom, m_ratio*m_zoom, -m_zoom, m_zoom, -10, 10);

        updateGL();

    }
}
void GLViewport::mouseMoveEvent(QMouseEvent *event){


    int dx = event->x() - last_pos.x();
    int dy = event->y() - last_pos.y();

    float units_per_pixel = m_zoom*2/m_height;
    if(m_state==V_IDLE){
        if (event->buttons() & Qt::MiddleButton) {
            m_cam_x-=dx*units_per_pixel;
            m_cam_y+=dy*units_per_pixel;
            updateGL();
        } else if(event->buttons() & Qt::LeftButton){
            m_c = Mouse2Screen(QPoint(event->pos()));
            m_state = V_BOXSELECTION;
        }
    }else if(m_state==V_BOXSELECTION){
        m_c2 = Mouse2Screen(QPoint(event->pos()));
        updateGL();
    }else if (m_state==V_ROTATING_0){
        m_c = Mouse2Screen(QPoint(event->pos()));

        m_state=V_ROTATING_1;

    }else if (m_state==V_ROTATING_1){

        m_c2 = Mouse2Screen(QPoint(event->pos()));
        if(m_ents->size()>0){
            float ox=m_ox;
            float oy=m_oy;

            QPointF v1(m_c.x()-ox,m_c.y()-oy);
            QPointF v2(m_c2.x()-ox,m_c2.y()-oy);

            float mod1 = sqrt(v1.x()*v1.x()+v1.y()*v1.y());
            float mod2 = sqrt(v2.x()*v2.x()+v2.y()*v2.y());

            float crossp = v1.x()*v2.y()-v1.y()*v2.x();
            if(mod1*mod2==0){
                m_alpha += 0;
            }else{
                float tp = (v1.x()*v2.x()+v1.y()*v2.y())/(mod1*mod2);
                if(tp>=1)
                    tp=0.9999;
                float temp_angle = acos(tp);

                if(crossp<0){
                    temp_angle*=-1;
                }

                m_alpha += temp_angle;
            }


            for(int i=0;i<m_temp_ents.size();i++){
                AED_Entity *ent = m_temp_ents[i];
                float dx = ent->o_pos_x-ox;
                float dy = ent->o_pos_y-oy;

                float radius = sqrt((dx*dx)+(dy*dy));

                ent->m_pos_x=cos(m_alpha+ent->o_alpha)*radius+ox;
                ent->m_pos_y=sin(m_alpha+ent->o_alpha)*radius+oy;

                ent->m_rot=ent->o_rot+m_alpha*180/M_PI;
            }
            m_c=m_c2;

            emit UpdateView();
            updateGL();

        }
    }else if (m_state==V_TRANSLATING){

        if(m_ents->size()>0){

            m_c.setX(m_c.x()+dx*units_per_pixel);
            m_c.setY(m_c.y()-dy*units_per_pixel);


            for(int i=0;i<m_temp_ents.size();i++){
                AED_Entity *ent = m_temp_ents[i];
                if(m_snap){
                    ent->m_pos_x=ent->o_pos_x+int(m_c.x());
                    ent->m_pos_y=ent->o_pos_y+int(m_c.y());
                }else{
                    ent->m_pos_x=ent->o_pos_x+m_c.x();
                    ent->m_pos_y=ent->o_pos_y+m_c.y();
                }

            }

            updateGL();

        }
    }else if (m_state==V_SCALING_0){
        m_c = Mouse2Screen(QPoint(event->pos()));

        m_state=V_SCALING_1;
    }else if (m_state==V_SCALING_1){
        m_c2 = Mouse2Screen(QPoint(event->pos()));

        float mod1 = (m_c.x()*m_c.x()+m_c.y()*m_c.y());
        float mod2 = (m_c2.x()*m_c2.x()+m_c2.y()*m_c2.y());

        float scaling_ratio = mod2/mod1;


        for(int i=0;i<m_temp_ents.size();i++){

            AED_Entity *ent = m_temp_ents[i];

            QPointF v(ent->o_pos_x-m_ox,ent->o_pos_y-m_oy);

            ent->m_pos_x=v.x()*scaling_ratio+m_ox;
            ent->m_pos_y=v.y()*scaling_ratio+m_oy;

            ent->m_size=ent->o_size*scaling_ratio;
        }

        updateGL();
    }
    last_pos = event->pos();
    emit UpdateView();
}

void GLViewport::mouseReleaseEvent(QMouseEvent *event){
    if(m_state==V_BOXSELECTION){
        if(event->button()==Qt::LeftButton){
            SelectEntityBox(m_c,m_c2);
            m_state = V_IDLE;
            updateGL();
        }
    }
}

void GLViewport::keyPressEvent(QKeyEvent *event){
    int key = event->key();

    switch(key){
    case Qt::Key_G:
        if(m_state==V_IDLE){
            m_temp_ents.clear();
            for(int i=0;i<m_ents->size();i++){
                if((*m_ents)[i].IsSelected()){
                    m_temp_ents.append(&(*m_ents)[i]);
                    (*m_ents)[i].o_pos_x=(*m_ents)[i].m_pos_x;
                    (*m_ents)[i].o_pos_y=(*m_ents)[i].m_pos_y;
                    m_c.setX(0);m_c.setY(0);
                }
            }
            if(!m_temp_ents.empty()){
                m_state=V_TRANSLATING;

            }
        }
        break;
    case Qt::Key_R:
        if(m_state ==V_IDLE){
            m_temp_ents.clear();
            m_ox = m_oy = 0;
            for(int i=0;i<m_ents->size();i++){
                if((*m_ents)[i].IsSelected()){
                    m_temp_ents.append(&(*m_ents)[i]);

                    (*m_ents)[i].o_pos_x=(*m_ents)[i].m_pos_x;
                    (*m_ents)[i].o_pos_y=(*m_ents)[i].m_pos_y;
                    (*m_ents)[i].o_rot=(*m_ents)[i].m_rot;
                    m_ox+=(*m_ents)[i].m_pos_x;
                    m_oy+=(*m_ents)[i].m_pos_y;

                }
            }

            if(!m_temp_ents.empty()){
                m_state=V_ROTATING_0;
                m_ox/=m_temp_ents.size();
                m_oy/=m_temp_ents.size();
                m_alpha = 0;
            }

            for(int i=0;i<m_temp_ents.size();i++){
                QPointF v1(1,0);
                QPointF v2((*m_ents)[i].o_pos_x-m_ox,(*m_ents)[i].o_pos_y-m_oy);

                float mod1 = sqrt(v1.x()*v1.x()+v1.y()*v1.y());
                float mod2 = sqrt(v2.x()*v2.x()+v2.y()*v2.y());

                float crossp = v1.x()*v2.y()-v1.y()*v2.x();
                if(mod1*mod2==0){
                    (*m_ents)[i].o_alpha = 0;
                }else{
                    (*m_ents)[i].o_alpha = acos((v1.x()*v2.x()+v1.y()*v2.y())/(mod1*mod2));

                    if(crossp<0)
                        (*m_ents)[i].o_alpha *=-1;
                }
            }
        }
        break;
    case Qt::Key_S:
        if(m_state ==V_IDLE){
            m_temp_ents.clear();
            m_ox = m_oy = 0;
            for(int i=0;i<m_ents->size();i++){
                if((*m_ents)[i].IsSelected()){
                    m_temp_ents.append(&(*m_ents)[i]);

                    (*m_ents)[i].o_pos_x=(*m_ents)[i].m_pos_x;
                    (*m_ents)[i].o_pos_y=(*m_ents)[i].m_pos_y;
                    (*m_ents)[i].o_size=(*m_ents)[i].m_size;
                    m_ox+=(*m_ents)[i].m_pos_x;
                    m_oy+=(*m_ents)[i].m_pos_y;
                    (*m_ents)[i].o_rot=(*m_ents)[i].m_rot;

                }
            }

            if(!m_temp_ents.empty()){
                m_alpha = 1;
                m_state=V_SCALING_0;
                m_ox/=m_temp_ents.size();
                m_oy/=m_temp_ents.size();

            }
        }


        break;
    case Qt::Key_Control:
        if(m_state==V_TRANSLATING){
            m_snap = 1;
        }
        break;
    case Qt::Key_Shift:
        m_shift = 1;
        break;
    case Qt::Key_D:
        if(m_shift){
            m_model->CopySelectedEntities();
            m_temp_ents.clear();
            for(int i=0;i<m_ents->size();i++){
                if((*m_ents)[i].IsSelected()){
                    m_temp_ents.append(&(*m_ents)[i]);
                    (*m_ents)[i].o_pos_x=(*m_ents)[i].m_pos_x;
                    (*m_ents)[i].o_pos_y=(*m_ents)[i].m_pos_y;
                    m_c.setX(0);m_c.setY(0);
                }
            }
            if(!m_temp_ents.empty()){
                m_state=V_TRANSLATING;

            }
        }
        break;
    default:
        QGLWidget::keyPressEvent(event);
    }

}

void GLViewport::keyReleaseEvent(QKeyEvent *event){
    int key = event->key();

    switch(key){
    case Qt::Key_Control:
        m_snap = 0;
        break;
    case Qt::Key_Shift:
        m_shift = 0;
        break;
    }

}

void GLViewport::focusInEvent(QFocusEvent *event){
    m_focused = true;

    qglClearColor(QColor::fromRgbF(0.7,0.7,0.7));
    updateGL();
}

void GLViewport::focusOutEvent(QFocusEvent *event){
    m_focused = false;
    m_state=V_NOFOCUS;
    qglClearColor(QColor::fromRgbF(0.5,0.5,0.5));
    updateGL();
}

QPointF GLViewport::Mouse2Screen(QPoint pos){
    int posx = pos.x()-m_width/2;
    int posy = m_height-(pos.y()+m_height/2);

    float units_per_pixel_x = m_ratio*m_zoom*2/m_width;
    float units_per_pixel_y = m_zoom*2/m_height;

    QPointF ret(posx*units_per_pixel_x+m_cam_x,posy*units_per_pixel_y+m_cam_y);

    return ret;
}

void GLViewport::SelectEntity(float x, float y){
    QVector<int> temp;

    for(int i=0;i<m_ents->size();i++){

        AED_Entity &ent = (*m_ents)[i];
        if(ent.IsSelected()){
            ent.SetSelected(false);
            temp.append(i);
        }

    }

    for(int i=0;i<m_ents->size();i++){
        AED_Entity &ent = (*m_ents)[i];
        if(!temp.count(i)){
            float ex = ent.m_pos_x;
            float ey = ent.m_pos_y;
            float radius = ent.m_size/2;

            if(x<ex+radius&&x>ex-radius&&y<ey+radius&&y>ey-radius){
                ent.SetSelected(true);
                emit ArchtypeChanged(ent.GetArchtype());
                break;
            }

        }

    }
    emit UpdateView();
    updateGL();

}

void GLViewport::SelectEntityPlus(float x, float y){

    for(int i=0;i<m_ents->size();i++){
        AED_Entity &ent = (*m_ents)[i];

        float ex = ent.m_pos_x;
        float ey = ent.m_pos_y;
        float radius = ent.m_size/2;

        if(x<ex+radius&&x>ex-radius&&y<ey+radius&&y>ey-radius){
            if(!ent.IsSelected())
                ent.SetSelected(true);
            else
                ent.SetSelected(false);
            break;
        }
        emit UpdateView();

    }
    updateGL();

}

void GLViewport::SelectEntityBox(QPointF c1, QPointF c2){
    float x1,x2,y1,y2;
    if(c1.x()<c2.x()){
        x1=c1.x();
        x2=c2.x();
    }else{
        x1=c2.x();
        x2=c1.x();
    }

    if(c1.y()<c2.y()){
        y1=c1.y();
        y2=c2.y();
    }else{
        y1=c2.y();
        y2=c1.y();
    }


    for(int i=0;i<m_ents->size();i++){
        AED_Entity &ent = (*m_ents)[i];
        if(!ent.IsSelected()){
            float ex=ent.m_pos_x;
            float ey=ent.m_pos_y;
            float radius = ent.m_size/2;

            if((ex+radius>x1)&&(ex-radius<x2)&&(ey+radius>y1)&&(ey-radius<y2)){
                ent.SetSelected(true);
            }
        }
    }
}

